#!/usr/bin/python
# -*- coding: utf-8 -*-

# This file is part of pyLot library.
#
# pyLot is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyLot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pyLot.  If not, see <http://www.gnu.org/licenses/>.

__author__ = u'Pierre Puiseux, Guillaume Baty'
__copyright__ = u"Copyright 2011-2012 (C) andheo, Université de Pau et des Pays de l'Adour"
__credits__ = [u'Pierre Puiseux', u'Guillaume Baty']
__license__ = "GNU Lesser General Public License"

__all__ = [u'GuiContainerEditor', 'GuiDialogContainerEditor']

from Qt4 import QtGui, QtCore
import copy

from pyLot.core import (PyLotDict, make_alias, check_value, ParameterSet, Datum)

from _pylot.qtgui.default.impl_widget import Widget
from _pylot.qtgui.widgets.gui_typedchoosers import make_chooser
from _pylot.qtgui.custom import DEFAULT_CUSTOM

def make_title(obj):
  return u'<u>%(title)s</u> :' % dict(title=make_alias(obj))

class ChooserContainer(QtGui.QWidget):
  def __init__(self):
    QtGui.QWidget.__init__(self)

    self.data_layout = QtGui.QGridLayout(self)
    self.data_layout.setContentsMargins(0, 0, 0, 0)
    self.data_layout.setSpacing(0)

    self.chooser_widget = QtGui.QWidget()
    self.chooser_layout = QtGui.QHBoxLayout(self.chooser_widget)
    self.chooser_layout.setContentsMargins(0, 0, 0, 0)
    self.chooser_layout.setSpacing(0)

    self.data_layout.addWidget(self.chooser_widget, 1, 1)

  def addChooser(self, chooser):
    self._chooser = chooser
    for widget in chooser.getPreWidgets():
      self.chooser_layout.addWidget(widget)
      self.chooser_layout.setSpacing(3)
    self.chooser_layout.addWidget(chooser.getDataWidget())
    for widget in chooser.getPostWidgets():
      self.chooser_layout.addWidget(widget)
      self.chooser_layout.setSpacing(3)

  def getChooser(self):
    return self._chooser

  def getSelector(self):
    return self._chooser.getDataWidget()

  def close(self):
    self._chooser.close()


class GuiContainerEditor(QtGui.QFrame, Widget):
  u"""
  Generate a widget to edit Container values
  GuiContainerEditor supports Container and ParameterSet
  
  .. warning ::
  
    You must **ABSOLUTELY** destroy this widget calling close method.
    Python garbage collector and Qt automatic destroy system produce
    errors !!

    For example, this function crashes application :
    
    def f(container):
      GuiContainerEditor(container)

    Indeed, in this sample, a QWidget is created and all datum of container are 
    connected to this widget. At exit, QWidget is deleted but GuiContainerEditor
    is not deleted due to garbage collector behaviour.
    Result is :
      - datum objects are still connected to GuiContainerEditor (python part)
      - if a datum changes, datum/GuiContainerEditor(python part) connection
      is called, and GuiContainerEditor(python part) updates its state
      calling GuiContainerEditor(C++ part) update methods.
      - those methods no more exists, and a an error is raised :
      "RuntimeError: Internal C++ object (Gui...Selector) already deleted."
      
    When GuiContainerEditor.close method is called, all datum connections are
    deleted and this problem does not occur.
    
  """
  StyleFrame = 'Frame'
  StyleFrameCheckable = 'FrameCheckable'
  StyleNoFrame = 'NoFrame'
  StyleFlat = "Flat"
  StyleFlatCheckable = 'FlatCheckable'
  StyleList = 'List'

  def __init__(self, container, style=StyleFlat, custom=DEFAULT_CUSTOM):
    QtGui.QFrame.__init__(self)
    Widget.__init__(self)

    self.__data = PyLotDict()

    self.data_widgets = []  # list of all formlayout

    self.custom = custom
    self.style = self.custom.getOption(u'style', self, container, style)

    alias = make_alias(container)
    self.container = container
    self.layout_box = QtGui.QGridLayout(self)

    # Create container
    if self.style in (self.StyleFlat, self.StyleFlatCheckable) :
      self.widget_frame = QtGui.QGroupBox(alias)
      self.widget_frame.setFlat(True)
      self.layout_frame = QtGui.QVBoxLayout(self.widget_frame)
    elif self.style == self.StyleNoFrame :
      self.widget_frame = QtGui.QWidget()
      self.layout_frame = QtGui.QVBoxLayout(self.widget_frame)
    elif self.style in (self.StyleFrame, self.StyleFrameCheckable) :
      self.widget_frame = QtGui.QGroupBox(alias)
      self.layout_frame = QtGui.QVBoxLayout(self.widget_frame)
    else :
      self.widget_frame = QtGui.QWidget()
      self.layout_frame = QtGui.QVBoxLayout(self.widget_frame)

    self.content = QtGui.QWidget()
    self.layout_content = QtGui.QVBoxLayout(self.content)

    widget_data = QtGui.QWidget()
    layout_data = QtGui.QFormLayout(widget_data)
    self.data_widgets.append(widget_data)

    self.layout_box.addWidget(self.widget_frame)
    self.layout_frame.addWidget(self.content)

    if self.style == self.StyleList :
      label = QtGui.QLabel(make_title(container))
      self.layout_content.addWidget(label)

    self.layout_content.addWidget(widget_data)

    for obj_id, obj in container.items():
      if isinstance(obj, Datum) :
        self.addDatum(layout_data, obj)
      else :
        self.addContainer(self.layout_content, obj)

        # prepare a new data_layout for next Datum objects
        widget_data = QtGui.QWidget()
        layout_data = QtGui.QFormLayout(widget_data)
        self.data_widgets.append(widget_data)
        self.layout_content.addWidget(widget_data)


    if self.style in (self.StyleFrameCheckable, self.StyleFlatCheckable) :
      self.widget_frame.setCheckable(True)
      self.widget_frame.toggled.connect(self.onFrameToggled)
      if isinstance(container, ParameterSet):
        checked = container.enabled
        container.connect(u'enabledChanged', self.onContainerEnabledChanged)
      else :
        checked = True
      self.onContainerEnabledChanged(container, None, checked)

    self.fineTune()

  @classmethod
  def getAvailableStyles(cls):
    return   [
              cls.StyleFlat,
              cls.StyleFlatCheckable,
              cls.StyleFrame,
              cls.StyleFrameCheckable,
              cls.StyleList,
              cls.StyleNoFrame,
              ]

  def toggle(self, state):
    self.widget_frame.setChecked(state)

  def onFrameToggled(self, state):
    self.container.enabled = state

  def hasEmptyFields(self):
    return self.container.hasEmptyFields()

  def listEmptyFieldPaths(self):
    return self.container.listEmptyFieldPaths()

  def onContainerEnabledChanged(self, container, old, new):
    if new is True :
      self.content.show()
    else :
      self.content.hide()
    self.widget_frame.setChecked(new)

  def keys(self):
    return self.__data.keys()

  def values(self):
    return self.__data.values()

  def __setitem__(self, key, value):
    self.__data[key] = value

  def __getitem__(self, key):
    return self.__data[key]

  def tuneLayoutData(self, layout_data):
    if self.style in [self.StyleList]:
      layout_data.setLabelAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
    else:
      layout_data.setLabelAlignment(QtCore.Qt.AlignVCenter)

    if self.style in (self.StyleFrame, self.StyleFrameCheckable):
      layout_data.setContentsMargins(0, 0, 0, 11)
    elif self.style == self.StyleNoFrame :
      layout_data.setContentsMargins(0, 11, 0, 11)
    else :
      layout_data.setContentsMargins(0, 11, 0, 11)

  def fineTune(self):
    self.layout_box.setContentsMargins(0, 0, 0, 0)
    self.layout_box.setSpacing(0)

    if self.style in (self.StyleFrame, self.StyleFrameCheckable):
      self.layout_frame.setContentsMargins(11, 11, 11, 11)
    elif self.style == self.StyleNoFrame :
      self.layout_frame.setContentsMargins(0, 0, 0, 0)
    else :
      self.layout_frame.setContentsMargins(11, 0, 11, 0)

    self.layout_frame.setSpacing(0)

    self.layout_content.setContentsMargins(0, 0, 0, 0)
    self.layout_content.setSpacing(0)

    for widget_data in self.data_widgets :
      self.tuneLayoutData(widget_data.layout())

#    self.layout_data.setFieldGrowthPolicy(QtGui.QFormLayout.FieldsStayAtSizeHint)

#    self.setSizePolicy(Preferred, Preferred)
#    self.content.setSizePolicy(MinimumExpanding, Preferred)
#    self.widget_data.setSizePolicy(MinimumExpanding, Preferred)
#    self.widget_frame.setSizePolicy(MinimumExpanding, Preferred)



  def addContainer(self, layout, container):
    if container.name is None or container.name in self.__data:
      return

    box = GuiContainerEditor(container, style=self.style, custom=self.custom)
    layout.addWidget(box)
    self.__data[container.name] = box

  def addDatum(self, layout, datum):
    ignore = self.custom.getOption('ignore_datum', self, datum, False)
    if ignore is True :
      return

    if datum.name is None or datum.name in self.__data :
      return

    chooser = make_chooser(datum, custom=self.custom)
    if chooser is None :
      return

    data_widget = ChooserContainer()

    label_widget = QtGui.QWidget()
#    label_widget.setSizePolicy(MinimumExpanding, Preferred)
    label_layout = QtGui.QGridLayout(label_widget)
    label_layout.setContentsMargins(0, 0, 0, 0)
    label_layout.setSpacing(0)

    if datum.tooltip :
      label_widget.setToolTip(datum.tooltip)

    data_widget.addChooser(chooser)
    has_label = self.custom.getOption(u'label', self, datum, True)
    if has_label and self.style == self.StyleList :
        alias = u'%(bullet)s %(label)s' % dict(bullet=u'• ',
                                               label=make_alias(datum))
    elif has_label :
      alias = u'%(label)s' % dict(label=make_alias(datum))
    else :
      alias = u''
    label_layout.addWidget(QtGui.QLabel(alias), 1, 1)

    enabled = self.custom.getOption(u'enabled', self, datum, None)
    if enabled is not None :
      label_widget.setEnabled(enabled)
      data_widget.setEnabled(enabled)

    _reversed = self.custom.getOption('reverse_label_data', self, datum, False)
    if _reversed is True :
      layout.addRow(data_widget, label_widget)
    else :
      layout.addRow(label_widget, data_widget)

    self.__data[datum.name] = (label_widget, data_widget)

  def setFieldValue(self, path, value):
    """
    Simulate an user click and value set. 
    """
    label, data = self[path]
    chooser = data.getChooser()
    chooser.setValue(value)

  def close(self):
    for label, box in self.__data.values() :
      box.close()

  def windowTitle(self):
    return make_alias(self.container)

  identifier = property(fget=lambda self:self.container.name)

class GuiDialogContainerEditor(QtGui.QDialog):

  u"""mode: modeless
  Original container is updated continuously.
  If data is updated outside, field change automatically.
  Internal container is original one, that is why updates are done continuously
  This mode is only standard GuiContainerEditor embedded in a QDialog.
  """
  ModeAutoSync = u'ModeAutoSync'

  u"""mode: modeless
  Container is updated only when user clicks on apply button. 
  Internal container is not original one but "apply" action update original
  one."""
  ModeManualSync = u'ModeManualSync'

  u"""mode: modal
  Container cannot be updated directly.
  Developer must ensure manual "post-mortem" update. 
  You can get user values using GuiDialogContainer.value() which returns
  internal container. Internal container is not original one"""
  ModeNoSync = u'ModeNoSync'

  def __init__(self, container, mode=ModeNoSync,
               custom=DEFAULT_CUSTOM):
    QtGui.QDialog.__init__(self)

    self.original = container

    if mode == self.ModeAutoSync :
      self.container = self.original
    elif mode in (self.ModeManualSync, self.ModeNoSync) :
      self.container = copy.deepcopy(self.original)
    else :
      check_value(u'mode', mode, [self.ModeAutoSync, self.ModeManualSync,
                                  self.ModeNoSync])

    self.editor = GuiContainerEditor(self.container, custom=custom)
    self.box_buttons = QtGui.QDialogButtonBox(self)
    self.box_buttons.setOrientation(QtCore.Qt.Horizontal)

    if mode == self.ModeAutoSync :
      self.setModal(True)
      raise NotImplementedError, self.ModeAutoSync
    elif mode == self.ModeManualSync :
      self.setModal(False)
      self.box_buttons.setStandardButtons(QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Close)
      self.connect(self.box_buttons, QtCore.SIGNAL("accepted()"), self.accept)
      self.connect(self.box_buttons, QtCore.SIGNAL("rejected()"), self.reject)
      self.pb_apply = self.box_buttons.button(QtGui.QDialogButtonBox.Apply)
      self.pb_apply.clicked.connect(self.actionApply)
    elif mode == self.ModeNoSync :
      self.setModal(False)
      self.box_buttons.setStandardButtons(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok)
      self.connect(self.box_buttons, QtCore.SIGNAL("accepted()"), self.accept)
      self.connect(self.box_buttons, QtCore.SIGNAL("rejected()"), self.reject)

    QtCore.QMetaObject.connectSlotsByName(self)

    self.resize(178, 70)
    self.layout_main = QtGui.QVBoxLayout(self)
    self.layout_main.addWidget(self.editor)
    self.layout_main.addWidget(self.box_buttons)

  def value(self):
    return self.container

  def actionApply(self):
    self.original.upgrade(self.container)

if __name__ == u'__main__' :
  pass
